
#include <jni.h>
#include <stdlib.h>
#include "usbhid.h"
#include "log.h"


#define DETACH_KERNEL_DRIVER


libusb_context *usb_context = NULL;
static int usbhid_init()
{
	if (NULL == usb_context)
	{
		return libusb_init(&usb_context);
	}

	return 1;
}
static void usbhid_exit()
{
	if (usb_context)
	{
		libusb_exit(usb_context);
		usb_context = NULL;
	}
}

usbhid_device *usbhid_open(unsigned short vid, unsigned short pid, int ifnum, libusb_transfer_cb_fn callback)
{
	usbhid_device *hid_dev = NULL;
	libusb_device_handle *device_handle = NULL;

	if (0!= usbhid_init())
	{
		E("%s : usbhid_init() failed", __FUNCTION__);
		return NULL;
	}

	device_handle = libusb_open_device_with_vid_pid(usb_context, vid, pid);
	if (device_handle)
	{
		int res, i, j;
		libusb_device *usb_dev = NULL;
		struct libusb_device_descriptor desc;
		struct libusb_config_descriptor *conf_desc = NULL;
		const struct libusb_interface *intf = NULL;

#ifdef DETACH_KERNEL_DRIVER
		/* Detach the kernel driver, but only if the device is managed by the kernel */
		if (1 == libusb_kernel_driver_active(device_handle, ifnum))
		{
			res = libusb_detach_kernel_driver(device_handle, ifnum);
			if (0 > res)
			{
				E("%s : libusb_detach_kernel_driver() failed", __FUNCTION__);
				libusb_close(device_handle);
				return NULL;
			}
		}
#endif

		res = libusb_claim_interface(device_handle, ifnum);
		if (0 > res)
		{
			E("%s : libusb_claim_interface() failed", __FUNCTION__);
			libusb_close(device_handle);
			return NULL;
		}

		usb_dev = libusb_get_device(device_handle);
		res = libusb_get_device_descriptor(usb_dev, &desc);
		if (0 > res)
		{
			E("%s : libusb_get_device_descriptor() failed", __FUNCTION__);
			libusb_close(device_handle);
			return NULL;
		}
		res = libusb_get_active_config_descriptor(usb_dev, &conf_desc);
		if (0 > res)
		{
			E("%s : libusb_get_active_config_descriptor() failed", __FUNCTION__);
			libusb_close(device_handle);
			return NULL;
		}

		intf = &conf_desc->interface[ifnum];
		for (i=0; i<intf->num_altsetting; i++)
		{
			const struct libusb_interface_descriptor *intf_desc = &intf->altsetting[i];
			if (LIBUSB_CLASS_HID == intf_desc->bInterfaceClass)
			{
				hid_dev = (usbhid_device *)malloc(sizeof(usbhid_device));
				memset(hid_dev, 0x00, sizeof(usbhid_device));

				hid_dev->device_handle      = device_handle;
				hid_dev->interface_num      = ifnum;
				hid_dev->manufacturer_index = desc.iManufacturer;
				hid_dev->product_index      = desc.iProduct;
				hid_dev->serial_index       = desc.iSerialNumber;

				/* Find the INPUT and OUTPUT endpoints. An OUTPUT endpoint is not required. */
				for (j=0; j<intf_desc->bNumEndpoints; j++)
				{
					const struct libusb_endpoint_descriptor *ep	= &intf_desc->endpoint[j];

					/* Determine the type and direction of this	endpoint. */
					int is_interrupt = (ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_INTERRUPT;
					int is_output = (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_OUT;
					int is_input = (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN;

					/* Decide whether to use it for intput or output. */
					if (hid_dev->input_endpoint == 0 && is_interrupt && is_input)
					{
						/* Use this endpoint for INPUT */
						hid_dev->input_endpoint = ep->bEndpointAddress;
						hid_dev->input_ep_max_packet_size = ep->wMaxPacketSize;
					}
					if (hid_dev->output_endpoint == 0 && is_interrupt && is_output)
					{
						/* Use this endpoint for OUTPUT */
						hid_dev->output_endpoint = ep->bEndpointAddress;
					}
				}
				
				/* Set up the transfer object. */
				unsigned char *packet_buf = malloc(hid_dev->input_ep_max_packet_size);
				hid_dev->transfer = libusb_alloc_transfer(0);
				libusb_fill_interrupt_transfer(hid_dev->transfer,
					hid_dev->device_handle,
					hid_dev->input_endpoint,
					packet_buf,
					hid_dev->input_ep_max_packet_size,
					callback,
					hid_dev,
					1000/*timeout*/);
				/* Make the first submission. Further submissions are made from inside read_callback() */
				libusb_submit_transfer(hid_dev->transfer);
				
				I("%s : usbhid_open() OK", __FUNCTION__);
				break;
			}
		}
	}
	else
	{
		E("%s : libusb_open_device_with_vid_pid(%04X, %04X, %d) failed", __FUNCTION__, vid, pid, ifnum);
	}

	return hid_dev;
}

void usbhid_close(usbhid_device *hid_dev)
{
	if (hid_dev)
	{
		if (hid_dev->transfer)
		{
			libusb_cancel_transfer(hid_dev->transfer);

			/* Clean up the Transfer objects allocated in read_thread(). */
			free(hid_dev->transfer->buffer);
			libusb_free_transfer(hid_dev->transfer);
		}

		/* release the interface */
		libusb_release_interface(hid_dev->device_handle, hid_dev->interface_num);

		/* Close the handle */
		libusb_close(hid_dev->device_handle);

		free(hid_dev);

		I("%s : OK", __FUNCTION__);
	}
	
	usbhid_exit();
}

int usbhid_thread(usbhid_device *hid_dev)
{
	if (usb_context)
	{
		//struct timeval tv = {60, 0};	/* tv_sec, tv_usec */
		//if (0 > libusb_handle_events_timeout_completed(usb_context, &tv, NULL))
		if (0 > libusb_handle_events(usb_context))
		{
			/* Cancel any transfer that may be pending. This call will fail if no transfers are pending, but that's OK. */
			if (0 == libusb_cancel_transfer(hid_dev->transfer))
			{
				/* The transfer was cancelled, so wait for its completion. */
				libusb_handle_events(usb_context);
				return -1;
			}
		}
	}

	return 0;
}

int usbhid_write(usbhid_device *hid_dev, int count, unsigned char *data)
{
	int report_number = data[0];
	int skipped_report_id = 0;
	int res;

	if (report_number == 0x0)
	{
		data++;
		count--;
		skipped_report_id = 1;
	}

	if (hid_dev->output_endpoint <= 0)
	{
		/* No interrput out endpoint. Use the Control Endpoint */
		res = libusb_control_transfer(hid_dev->device_handle,
			LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT,
			0x09/*HID Set_Report*/,
			(2/*HID output*/ << 8) | report_number,
			hid_dev->interface_num,
			(unsigned char *)data, count,
			1000/*timeout millis*/);
		if (res < 0)
		{
			E("%s : libusb_control_transfer failed", __FUNCTION__);
			return -1;
		}
	}
	else
	{
		/* Use the interrupt out endpoint */
		int actual_length;
		res = libusb_interrupt_transfer(hid_dev->device_handle,
			hid_dev->output_endpoint,
			(unsigned char *)data,
			count,
			&actual_length,
			1000);
		if (res < 0)
		{
			E("%s : libusb_control_transfer failed", __FUNCTION__);
			return -1;
		}

		count = actual_length;
	}

	if (skipped_report_id)
	{
		count++;
	}

	return count;
}
